import axios, { AxiosRequestConfig, AxiosResponse, AxiosError } from "axios";
import Qs from "qs";
// import { ElMessage } from 'element-plus'
import router from "@/router";
import storage from "@/utils/storage";
import store from "@/store";

// 取消重复请求
// 声明一个 Map 用于存储每个请求的标识 和 取消函数
const pendingRequest = new Map();
/**
 * 转化请求参数
 * @param config 请求参数
 * @returns 返回转后的参数
 */
const generateReqKey = (config: AxiosRequestConfig) => {
  const { method, url, params, data } = config;
  return [method, url, Qs.stringify(params), Qs.stringify(data)].join("&");
};
/**
 * 存储请求信息
 * @param config 请求参数
 */
const addPendingRequest = (config: AxiosRequestConfig) => {
  const requestKey = generateReqKey(config);
  config.cancelToken =
    config.cancelToken ||
    new axios.CancelToken(cancel => {
      if (!pendingRequest.has(requestKey)) {
        pendingRequest.set(requestKey, cancel);
      }
    });
};
/**
 * 移除重复请求
 * @param config 请求参数
 */
const removePendingRequest = (config: AxiosRequestConfig) => {
  const requestKey = generateReqKey(config);
  if (pendingRequest.has(requestKey)) {
    const cancelToken = pendingRequest.get(requestKey);
    cancelToken(requestKey);
    pendingRequest.delete(requestKey);
  }
};
/**
 * 清空 pendingRequest 中的请求（在路由跳转时调用）
 */
export const clearPendingRequest = () => {
  for (const [requestKey, cancel] of pendingRequest) {
    cancel(requestKey);
  }
  pendingRequest.clear();
};

const axiosRetryInterceptor = (error: any) => {
  // 超时重新请求
  const config = error.config;
  if (!config || !config.retry) return Promise.reject(error);

  const { response } = error;
  // 全局的请求次数,请求的间隙
  const [RETRY_COUNT, RETRY_DELAY] = [3, 1000];

  if (config && RETRY_COUNT) {
    // 设置用于跟踪重试计数的变量
    config.__retryCount = config.__retryCount || 0;
    // 检查是否已经把重试的总数用完
    if (config.__retryCount >= RETRY_COUNT) {
      // ElMessage.error('请求超时，请检查网络情况！')
      return Promise.reject(response || { message: error.message });
    }
    // 增加重试计数
    config.__retryCount++;
    // 创造新的Promise来处理指数后退
    const backoff = new Promise(resolve => {
      window.setTimeout(() => {
        console.log(
          `接口${config.url}请求超时，第${config.__retryCount +
            1}次重新请求中。。`
        );
        resolve(1);
      }, RETRY_DELAY || 1);
    });
    // instance重试请求的Promise
    return backoff.then(() => {
      return instance(config);
    });
  }
};

/**
 * 提示函数
 * 禁止点击蒙层、显示一秒后关闭
 */
const tip = (msg: string) => {
  // ElMessage.error(msg)
};
/**
 * 跳转登录页
 * 携带当前页面路由，以期在登录页面完成登录后返回当前页面
 */
const toLogin = () => {
  router.replace({
    path: "/login",
    query: {
      redirect: (router.currentRoute as any).fullPath
    }
  });
};

/**
 * 请求失败后的错误统一处理
 * @param {Number} status 请求失败的状态码
 */
const errorHandle = (status: number, other: string) => {
  // 状态码判断
  switch (status) {
    // 401: 未登录状态，跳转登录页
    case 401:
      // ElMessage.warning('未登录，请登录!')
      toLogin();
      break;
    // 403 token过期
    // 清除token并跳转登录页
    case 403:
      // ElMessage.warning('登录过期，请重新登录!')
      storage.remove("token");
      store.commit("CHANGELOGIN", false);
      setTimeout(() => {
        toLogin();
      }, 1000);
      break;
    // 404请求不存在
    case 404:
      tip("请求的资源不存在！");
      break;
    case 500:
      tip("服务器错误,请联系管理员！");
      break;
    default:
      tip(other);
  }
};

// axios 实例
const instance = axios.create({
  // baseURL: '/api/',
  timeout: 1000 * 8,
  responseType: "json",
  // 允许跨域携带cookie信息
  // withCredentials: true,
  headers: {
    // 'X-Requested-With': 'XMLHttpRequest', 自定义字段
    // 'x-content-type-options': 'nosniff',
    "X-Requested-With": "XMLHttpRequest"
  }
});
// 环境的切换
if (import.meta.env.MODE == "development") {
  instance.defaults.baseURL = "/api";
} else if (import.meta.env.MODE == "test") {
  instance.defaults.baseURL = "https://api.cctvch.cn/api.php";
} else if (import.meta.env.MODE == "production") {
  instance.defaults.baseURL = "https://api.cctvch.cn/api.php";
}

instance.defaults.headers.post["Content-Type"] =
  "application/x-www-form-urlencoded;charset=UTF-8";
instance.defaults.headers.get["Content-Type"] =
  "application/json;charset=UTF-8";

instance.interceptors.request.use(
  (config: AxiosRequestConfig) => {
    // (此处可以放公共加载状态提示)
    // loadingInstance = Loading.service({
    //   text: '加载中',
    //   background: 'rgba(0, 0, 0, 0.3)'
    // });

    // 检查是否存在重复请求，若存在则取消已发的请求
    removePendingRequest(config);
    // 把当前请求信息添加到pendingRequest对象中
    addPendingRequest(config);

    // 登录流程控制中，根据本地是否存在token判断用户的登录情况
    // 但是即使token存在，也有可能token是过期的，所以在每次的请求头中携带token
    // 后台根据携带的token判断用户的登录情况，并返回给我们对应的状态码
    // 而后我们可以在响应拦截器中，根据状态码进行一些统一的操作。
    // config.headers['X-Requested-Sign']
    const token = store.getters.token;
    token && config.headers && (config.headers.Authorization = token);

    // 防止post请求参数无法传到后台  在这里就用到了qs
    if (
      config.method === "post" &&
      config.headers &&
      config.headers["Content-Type"] !== "multipart/form-data"
    ) {
      config.data = Qs.stringify(config.data);
    }
    return config;
  },
  (error: AxiosError) => {
    return Promise.reject(error);
  }
);

instance.interceptors.response.use(
  // 请求成功
  // 从pendingRequest对象中移除请求
  (response: AxiosResponse) => (
    removePendingRequest(response.config),
    response.status === 200
      ? Promise.resolve(response)
      : Promise.reject(response)
  ),
  // 请求失败
  (error: AxiosError) => {
    // 从pendingRequest对象中移除请求
    removePendingRequest(error.config || {});
    if (
      error.code === "ECONNABORTED" ||
      error.message === "Network Error" ||
      error.message.includes("timeout")
    ) {
      return axiosRetryInterceptor(error);
    }
    const { response } = error;
    if (response) {
      const { status, data } = response;
      // 请求已发出，但是不在2xx的范围
      errorHandle(status, (data as any)?.message || "");
      return Promise.reject(response);
    } else {
      // 处理断网的情况
      // eg:请求超时或断网时，更新state的network状态
      // network状态在app.vue中控制着一个全局的断网提示组件的显示隐藏
      // 关于断网组件中的刷新重新获取数据，会在断网组件中说明
      if (!window.navigator.onLine) {
        store.commit("CHANGENETWORK", false);
        tip("网络异常!");
        router.push({ path: "/network" });
      } else {
        return Promise.reject(error);
      }
    }
  }
);
export default instance;

/**
 * get方法，对应get请求
 * @param {String} url [请求的url地址]
 * @param {Object} params [请求时携带的参数]
 */
export function get(url: string, params = {}) {
  return new Promise((resolve, reject) => {
    instance
      .get(url, { params })
      .then(res => {
        resolve(res.data.data);
      })
      .catch(err => {
        reject(err.data);
      });
  });
}

/**
 * post方法，对应post请求
 * @param {String} url [请求的url地址]
 * @param {Object} data [请求时携带的参数]
 */
export function post(url: string, data = {}) {
  return new Promise((resolve, reject) => {
    instance
      .post(url, data)
      .then(res => {
        resolve(res.data.data);
      })
      .catch(err => {
        reject(err.data);
      });
  });
}

/**
 * upload方法，对应post请求
 * @param {String} url [请求的url地址]
 * @param {Object} data [请求时携带的参数]
 */
export function upload(url: string, data: any) {
  const config: AxiosRequestConfig = {
    url,
    method: "post",
    data,
    headers: {
      Accept: "application/json",
      "Content-Type": "multipart/form-data"
      // withCredentials: true
    }
  };
  return new Promise((resolve, reject) => {
    axios
      .request(config)
      .then(res => {
        resolve(res.data);
      })
      .catch(err => {
        reject(err);
      });
  });
}

/*
 * get方法，下载文件
 * @params {string} url 请求地址
 * @params {object} resOpts 请求配置参数
 */
export const download = (url: string) => {
  const config: AxiosRequestConfig = {
    url,
    method: "get",
    data: "",
    responseType: "blob",
    headers: {
      Accept: "application/json",
      "Content-Type": "application/json; charset=utf-8"
      // withCredentials: true
    }
  };
  return new Promise((resolve, reject) => {
    axios
      .request(config)
      .then(response => {
        // 创建新的URL并指向File对象或者Blob对象的地址
        const url = window.URL.createObjectURL(new Blob([response.data]));
        // 创建a标签，用于跳转至下载链接
        const link = document.createElement("a");
        link.style.display = "none";
        link.href = url;
        link.setAttribute("download", `${new Date().getTime()}.csv`);
        // 兼容：某些浏览器不支持HTML5的download属性
        if (typeof link.download === "undefined") {
          link.setAttribute("target", "_blank");
        }
        // 挂载a标签
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);
        // 释放blob URL地址
        window.URL.revokeObjectURL(url);
        resolve(response);
      })
      .catch(err => {
        reject(err);
      });
  });
};
